/*
 * Warning: C++ ahead.
 */
#include "at.h"

#include <iostream>

#include <cstdio>
#include <cstdlib>
#include <ctime>

#define BUGOUT(s) std::cout << "DEBUG: " << s << std::endl

class Input
{
public:
    static int whitekey;
    static int blackkey;
    
    static void OnKeyPress()
    {
        switch (atKey())
        {
        case 'j':
            whitekey = 'j';
            break;
        case 'l':
            whitekey = 'l';
            break;
        case 'i':
            whitekey = 'i';
            break;
        case 'k':
            whitekey = 'k';
            break;
        case 'a':
            blackkey = 'a';
            break;
        case 'd':
            blackkey = 'd';
            break;
        case 'w':
            blackkey = 'w';
            break;
        case 's':
            blackkey = 's';
            break;
        default:
            break;
        }
    }
};

int Input::whitekey = ATK_NONE;
int Input::blackkey = ATK_NONE;

class Field
{
public:
    static const int WHITE = 0;
    static const int BLACK = 1;

    Field(int width, int height, int blocksize)
        : m_width(width), m_height(height), m_blocksize(blocksize)
    {
        m_array = new int[m_width * m_height];

        Reset();
    }
    virtual ~Field() { delete [] m_array; }

    virtual int Width() const { return m_width; }
    virtual int Height() const  { return m_height; }
    virtual int BlockSize() const { return m_blocksize; }
    virtual int &ColorAt(int x, int y) { return m_array[y * m_width + x]; }
    virtual void Draw()
    {
        atSetCellSize(m_blocksize, m_blocksize);
        for (int x = 0; x < m_width; ++x)
        {
            for (int y = 0; y < m_height; ++y)
            {
                if (ColorAt(x, y) == BLACK)
                    atSetColor(AT_FG, ATC_BLACK);
                else if (ColorAt(x, y) == WHITE)
                    atSetColor(AT_FG, ATC_WHITE);
                atDrawCell(atMainSurface(), x, y);
            }
        }
    }

    virtual void Reset()
    {
        for (int x = 0; x < m_width; ++x)
            for (int y = 0; y < m_height/2; ++y)
                ColorAt(x, y) = BLACK;
        for (int x = 0; x < m_width; ++x)
            for (int y = m_height/2; y < m_height; ++y)
                ColorAt(x, y) = WHITE;
    }
protected:
    int m_width, m_height;
    int m_blocksize;
    int *m_array;
};

int ReverseColor(int color)
{
    if (color == Field::BLACK)
        return Field::WHITE;
    return Field::BLACK;
}

class Tetromino
{
public:
    Tetromino(Field &field, int color)
        : m_field(field), m_n(4), m_color(color)
    {
        m_x = field.Width() / 2 - 2;
        if (m_color == Field::WHITE)
            m_y = -4;
        else if (m_color == Field::BLACK)
            m_y = field.Height() - 1 + 3;
        Clear();
    }
    virtual ~Tetromino() { }
    
    virtual int &ColorAt(int x, int y) { return m_array[y][x]; }
    virtual int &X() { return m_x; }
    virtual int &Y() { return m_y; }
    virtual int &N() { return m_n; }
    virtual void Clear()
    {
        for (int x = 0; x < 4; ++x)
            for (int y = 0; y < 4; ++y)
                m_array[y][x] = ReverseColor(m_color);
    }
    virtual void Draw()
    {
        atSetCellSize(m_field.BlockSize(), m_field.BlockSize());
        if (m_color == Field::BLACK)
            atSetColor(AT_FG, ATC_BLACK);
        else if (m_color == Field::WHITE)
            atSetColor(AT_FG, ATC_WHITE);
        for (int x = 0; x < 4; ++x)
        {
            for (int y = 0; y < 4; ++y)
            {
                if (m_array[y][x] == m_color)
                    atDrawCell(atMainSurface(), m_x + x, m_y + y);
            }
        }
    }

    virtual void SetDown()
    {
        for (int x = 0; x < 4; ++x)
        {
            for (int y = 0; y < 4; ++y)
            {
                if (m_x + x >= 0 && m_x + x < m_field.Width() &&
                    m_y + y >= 0 && m_y + y < m_field.Height())
                if (m_array[y][x] == m_color)
                    m_field.ColorAt(m_x + x, m_y + y) = m_color;
            }
        }
    }

    virtual bool MoveDown()
    {
        if (m_color == Field::WHITE)
        {
            m_y += 1;
            if (!IsValid(m_array))
            {
                m_y -= 1;
                return false;
            }
        }
        else if (m_color == Field::BLACK)
        {
            m_y -= 1;
            if (!IsValid(m_array))
            {
                m_y += 1;
                return false;
            }
        }

        return true;
    }

    virtual bool MoveLeft()
    {
        m_x -= 1;
        if (!IsValid(m_array))
        {
            m_x += 1;
            return false;
        }

        return true;
    }
    virtual bool MoveRight()
    {
        m_x += 1;
        if (!IsValid(m_array))
        {
            m_x -= 1;
            return false;
        }

        return true;
    }
    
    virtual bool Rotate()
    {
        int c = ReverseColor(m_color);
        
        int tmp[4][4] = {
            c, c, c, c,
            c, c, c, c,
            c, c, c, c,
            c, c, c, c
        };
        for (int y = 0; y < m_n; ++y)
            for (int x = 0; x < m_n; ++x)
                tmp[y][x] = m_array[x][m_n - 1 - y];

        if (IsValid(tmp))
        {
            for (int y = 0; y < 4; ++y)
                for (int x = 0; x < 4; ++x)
                    m_array[y][x] = tmp[y][x];
            return true;
        }
        else
        {
            return false;
        }
    }
    
    virtual bool InField() const
    {
        for (int y = 0; y < 4; ++y)
            for (int x = 0; x < 4; ++x)
                if (m_array[y][x] == m_color)
                    if (m_x + x < 0 || m_x + x >= m_field.Width() ||
                        m_y + y < 0 || m_y + y >= m_field.Height())
                            return false;
        return true;
    }
protected:
    virtual bool IsValid(int arr[4][4]) const
    {
        for (int y = 0; y < 4; ++y)
        {
            for (int x = 0; x < 4; ++x)
            {
                if (arr[y][x] == m_color)
                if ( m_x + x >= 0 && m_x + x < m_field.Width())
                {
                    if (m_y + y >= 0 && m_y + y < m_field.Height())
                    if (arr[y][x] == m_field.ColorAt(x + m_x, y + m_y))
                        return false;
                }
                else
                {
                    return false;
                }
            }
        }

        return true;
    }

    Field &m_field;
    int m_n; /* Used for rotation. */
    int m_x, m_y;
    int m_color;
    int m_array[4][4];
};

class IPiece : public Tetromino
{
public:
    IPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated IPiece");
        m_array[0][1] = color;
        m_array[1][1] = color;
        m_array[2][1] = color;
        m_array[3][1] = color;
        N() = 4;
    }
    virtual ~IPiece() { }
protected:
};

class JPiece : public Tetromino
{
public:
    JPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated JPiece");
        m_array[0][1] = color;
        m_array[1][1] = color;
        m_array[2][1] = color;
        m_array[2][0] = color;
        N() = 3;
    }
    virtual ~JPiece() { }
protected:
};

class LPiece : public Tetromino
{
public:
    LPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated LPiece");
        m_array[0][1] = color;
        m_array[1][1] = color;
        m_array[2][1] = color;
        m_array[2][2] = color;
        N() = 3;
    }
    virtual ~LPiece() { }
protected:
};

class OPiece : public Tetromino
{
public:
    OPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated OPiece");
        m_array[0][0] = color;
        m_array[0][1] = color;
        m_array[1][0] = color;
        m_array[1][1] = color;
        N() = 2;
    }
    virtual ~OPiece() { }
protected:
};

class SPiece : public Tetromino
{
public:
    SPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated SPiece");
        m_array[2][0] = color;
        m_array[2][1] = color;
        m_array[1][1] = color;
        m_array[1][2] = color;
        N() = 3;
    }
    virtual ~SPiece() { }
protected:
};

class ZPiece : public Tetromino
{
public:
    ZPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated ZPiece");
        m_array[1][0] = color;
        m_array[1][1] = color;
        m_array[2][1] = color;
        m_array[2][2] = color;
        N() = 3;
    }
    virtual ~ZPiece() { }
protected:
};

class TPiece : public Tetromino
{
public:
    TPiece(Field &field, int color) : Tetromino(field, color)
    {
        BUGOUT("Generated TPiece");
        m_array[0][1] = color;
        m_array[1][0] = color;
        m_array[1][1] = color;
        m_array[1][2] = color;
        N() = 3;
    }
    virtual ~TPiece() { }
protected:
};

class TetrominoFactory
{
public:
    enum
    {
        I, J, L, O, S, Z, T, MAX
    };
    
    static Tetromino *CreateTetromino(int piece, Field &field, int color)
    {
        switch (piece)
        {
            case I:
                return new IPiece(field, color);
                break;
            case J:
                return new JPiece(field, color);
                break;
            case L:
                return new LPiece(field, color);
                break;
            case O:
                return new OPiece(field, color);
                break;
            case S:
                return new SPiece(field, color);
                break;
            case Z:
                return new ZPiece(field, color);
                break;
            case T:
                return new TPiece(field, color);
                break;
            default:
                return NULL;
                break;
        }
    }
};

class Game
{
public:
    static const int WIDTH = 12;
    static const int HEIGHT = 16;
    static const char *TITLE;
    
    Game(int blocksize)
        : m_score(0), m_blocksize(blocksize), m_whitepiece(NULL), m_blackpiece(NULL)
    {
		srand((unsigned int)time(0));
        
        atSetCellSize(blocksize, blocksize);
        atStart(TITLE, WIDTH, HEIGHT);
        atSetOnKeyPress(Input::OnKeyPress);
        m_glyphs = atDefaultGlyphs();
        atSetGlyphs(m_glyphs);
        
        m_field = new Field(WIDTH, HEIGHT, blocksize);
    }
    virtual ~Game()
    {
        if (m_whitepiece)
            delete m_whitepiece;
        if (m_blackpiece)
            delete m_blackpiece;
        delete m_field;
        atDelGlyphs(m_glyphs);
        atSetGlyphs(m_glyphs);
        atStop();
    }
    
    virtual void Update(int counter)
    {
        if (!m_whitepiece)
            m_whitepiece = TetrominoFactory::CreateTetromino(rand() % TetrominoFactory::MAX, *m_field, Field::WHITE);
        if (!m_blackpiece)
            m_blackpiece = TetrominoFactory::CreateTetromino(rand() % TetrominoFactory::MAX, *m_field, Field::BLACK);

        if (counter % 20 == 0)
        {
            if (!m_whitepiece->MoveDown())
            {
                m_whitepiece->SetDown();
                
                if (!m_whitepiece->InField())
                    atKill();
                
                delete m_whitepiece;
                m_whitepiece = NULL;
                m_score++;
            }
            if (!m_blackpiece->MoveDown())
            {
                m_blackpiece->SetDown();
                
                if (!m_blackpiece->InField())
                    atKill();
                
                delete m_blackpiece;
                m_blackpiece = NULL;
                m_score++;
            }
        }

        if (Input::whitekey != ATK_NONE)
        {
            switch (Input::whitekey)
            {
            case 'j':
                if (m_whitepiece) m_whitepiece->MoveLeft();
                break;
            case 'l':
                if (m_whitepiece) m_whitepiece->MoveRight();
                break;
            case 'i':
                if (m_whitepiece) m_whitepiece->Rotate();
                break;
            case 'k':
                if (m_whitepiece) m_whitepiece->MoveDown();
                break;
            default:
                break;
            }
            
            Input::whitekey = ATK_NONE;
        }
        if (Input::blackkey != ATK_NONE)
        {
            switch (Input::blackkey)
            {
            case 'a':
                if (m_blackpiece) m_blackpiece->MoveLeft();
                break;
            case 'd':
                if (m_blackpiece) m_blackpiece->MoveRight();
                break;
            case 'w':
                if (m_blackpiece) m_blackpiece->Rotate();
                break;
            case 's':
                if (m_blackpiece) m_blackpiece->MoveDown();
                break;
            default:
                break;
            }
            
            Input::blackkey = ATK_NONE;
        }
    }

    virtual void Render()
    {
        char buf[64]; 
        
        sprintf(buf, "Score: %d", m_score);
        
        m_field->Draw();
        if (m_whitepiece) m_whitepiece->Draw();
        if (m_blackpiece) m_blackpiece->Draw();
        
        atSetCellSize(AT_DEFAULT_CELL_WIDTH, AT_DEFAULT_CELL_HEIGHT);
        atSetColor(AT_FG, ATC_RED);
        atDrawString(atMainSurface(), 0, 0, AT_FG, buf);
        
        atUpdate();
    }
    
    virtual void GameOver()
    {
        char buf[64]; 
        
        atClearSurface(atMainSurface());
        
        sprintf(buf, "Final score: %d", m_score);
        
        atSetCellSize(m_blocksize, m_blocksize);
        atSetColor(AT_FG, ATC_WHITE);
        atSetGlyphScale(m_glyphs, 2);
        atDrawString(atMainSurface(), WIDTH/4 + 1, HEIGHT/2 - 1, AT_FG, buf);
        
        atSetColor(AT_FG, ATC_RED);
        sprintf(buf, "Press any key to quit.", m_score);

        atDrawString(atMainSurface(), WIDTH/4, HEIGHT/2 + 0, AT_FG, buf);
        
        atUpdate();
    }
    
    virtual void Run()
    {
        unsigned long start = 0;
        unsigned long end;
        unsigned long counter = 0;
        while (!atClosed())
        {
            atGrabEvents();

            end = atTicks();
            while (end - start > 50)
            {
                Update(counter++);
                start += 50;
            }

            Render();
        }
        
        atRevive();
        
        while (!atClosed())
        {
            atGrabEvents();
            if (atKeyPressed())
                break;
            GameOver();
        }
    }
protected:
    Field *m_field;
    atGlyphs_T *m_glyphs;
    int m_score;
    Tetromino *m_whitepiece;
    Tetromino *m_blackpiece;
    int m_blocksize;
};

const char *Game::TITLE = "Negation";

int main()
{
    Game game(32);
    
    game.Run();
    return 0;
}
